// #include <rclcpp/rclcpp.hpp>
#include <ament_index_cpp/get_package_share_directory.hpp>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <fstream> 
#include <iostream>
#include <spdlog/spdlog.h>
#include <filesystem>

namespace fs = std::filesystem;

static const std::unordered_map<std::string, spdlog::level::level_enum> level_map = {
  {"trace", spdlog::level::trace},
  {"debug", spdlog::level::debug},
  {"info", spdlog::level::info},
  {"warn", spdlog::level::warn},
  {"warning", spdlog::level::warn},
  {"error", spdlog::level::err},
  {"err", spdlog::level::err},
  {"critical", spdlog::level::critical},
  {"off", spdlog::level::off}
};

struct GlobalConfig {
    spdlog::level::level_enum log_level = spdlog::level::info;
    bool async = true;
    spdlog::level::level_enum flush_level = spdlog::level::warn;
    size_t max_file_size = 10 * 1024 * 1024;  // 10MB
    int max_files = 3;
};

struct NodeConfig : public GlobalConfig {
    std::string node_name;
  
    bool log_level_set = false;
    bool flush_level_set = false;
    bool async_set = false;
    bool max_file_size_set = false;
    bool max_files_set = false;
};

std::ostream& operator<<(std::ostream& os, const GlobalConfig& config) {
    os << "GlobalConfig:\n"
       << "  log_level: " << std::string(spdlog::level::to_string_view(config.log_level).data()) << "\n"
       << "  async: " << (config.async ? "true" : "false") << "\n"
       << "  flush_level: " << std::string(spdlog::level::to_string_view(config.flush_level).data()) << "\n"
       << "  max_file_size: " << config.max_file_size << " bytes\n"
       << "  max_files: " << config.max_files << "\n";
    return os;
}

std::ostream& operator<<(std::ostream& os, const NodeConfig& config) {
    os << static_cast<const GlobalConfig&>(config)
       << "NodeConfig:\n"
       << "  node_name: " << config.node_name << "\n";
    return os;
}


const auto get_log_config_path = [](const std::string& package_name) {
    std::string default_path = "/opt/ros/humble/share/rcl_logging_spdlog/config/logconfig.ini";
    
    if (const char* env_path = std::getenv("ROS2_LOG_CONFIG")) {
        if (!std::string(env_path).empty()) {
            return fs::path(env_path).lexically_normal().string();
        }
    }

    try {
        return (fs::path(ament_index_cpp::get_package_share_directory(package_name)) 
               / "config" / "logconfig.ini").lexically_normal().string();
    } catch (const std::exception& e) {
        std::cerr << "Package path resolution failed: " << e.what() << "\n";
        return default_path;
    }
};

class LogConfigParser {
public:
  static LogConfigParser& getInstance(const std::string& package_name) {
    std::call_once(init_flag_, &LogConfigParser::init, package_name);
    return *instance_;
  }

  NodeConfig get_node_config(const std::string& node_name) {
    NodeConfig config;
    static_cast<GlobalConfig&>(config) = global_config_;  
    config.node_name = node_name;

    if (node_configs_.count(node_name)) {
      const auto& node_cfg = node_configs_.at(node_name);
      if (node_cfg.log_level_set) config.log_level = node_cfg.log_level;
      if (node_cfg.async_set) config.async = node_cfg.async;
      if (node_cfg.flush_level_set) config.flush_level = node_cfg.flush_level;
      if (node_cfg.max_file_size_set) config.max_file_size = node_cfg.max_file_size;
      if (node_cfg.max_files_set) config.max_files = node_cfg.max_files;
    }

    return config;
  }

  LogConfigParser(const LogConfigParser&) = delete;
  LogConfigParser& operator=(const LogConfigParser&) = delete;

private:
  static std::unique_ptr<LogConfigParser> instance_;
  static std::once_flag init_flag_;

  explicit LogConfigParser(const std::string& package_name) {
    const std::string config_path = get_log_config_path(package_name);
    parse_config_file(config_path);
  }

  static void init(const std::string& package_name) {
    instance_.reset(new LogConfigParser(package_name));
  }

  static spdlog::level::level_enum string_to_level(const std::string& level_str) {
    std::string lower_str;
    lower_str.reserve(level_str.size());
    for (char c : level_str) {
      lower_str.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));
    }

    auto it = level_map.find(lower_str);
    if (it != level_map.end()) {
      return it->second;
    }
    throw std::invalid_argument("Invalid log level: " + level_str);
  }

  void parse_config_file(const std::string& path) {
    std::ifstream file(path);
    if (!file.is_open()) {
      throw std::runtime_error("Cannot open config file: " + path);
    }

    std::string current_section;
    std::string line;

    while (std::getline(file, line)) {
      line = trim(line);
      if (line.empty() || line[0] == '#') continue;

      if (line[0] == '[' && line.back() == ']') {
        current_section = line.substr(1, line.size() - 2);
        continue;
      }

      size_t delimiter_pos = line.find('=');
      if (delimiter_pos == std::string::npos) continue;

      std::string key = trim(line.substr(0, delimiter_pos));
      std::string value = trim(line.substr(delimiter_pos + 1));

      if (current_section == "global") {
        handle_global_config(key, value);
      } else if (current_section.find("node.") == 0) {
        std::string node_name = current_section.substr(5);
        handle_node_config(node_name, key, value);
      }
    }

    // std::cout << global_config_ << std::endl;

    // for (const auto& pair : node_configs_) {
    //   std::cout << "Key: " << pair.first << "\n" << pair.second << std::endl;
    // }
  }

  void handle_global_config(const std::string& key, const std::string& value) { 
    if (key == "log_level") {
      global_config_.log_level = string_to_level(value);
    } else if (key == "async") {
      global_config_.async = (value == "true");
    } else if (key == "flush_level") {
      global_config_.flush_level = string_to_level(value);
    } else if (key == "max_file_size") {
      global_config_.max_file_size = parse_size(value);
    } else if (key == "max_files") {
      global_config_.max_files = std::stoi(value);
    }
  }

  void handle_node_config(const std::string& node_name, const std::string& key, const std::string& value) { 
    NodeConfig& cfg = node_configs_[node_name];
    if (cfg.node_name != node_name) {
        cfg.node_name = node_name;
        cfg.log_level = global_config_.log_level;
        cfg.flush_level = global_config_.flush_level;
        cfg.async = global_config_.async;
        cfg.max_file_size = global_config_.max_file_size;
        cfg.max_files = global_config_.max_files;
        cfg.log_level_set = cfg.flush_level_set = cfg.async_set = cfg.max_file_size_set = cfg.max_files_set = false;
    }

    if (key == "log_level") {
        cfg.log_level = string_to_level(value);
        cfg.log_level_set = true;
    } else if (key == "async") {
        cfg.async = (value == "true");
        cfg.async_set = true;
    } else if (key == "flush_level") {
        cfg.flush_level = string_to_level(value);
        cfg.flush_level_set = true;
    } else if (key == "max_file_size") {
        cfg.max_file_size = parse_size(value);
        cfg.max_file_size_set = true;
    } else if (key == "max_files") {
        cfg.max_files = std::stoi(value);
        cfg.max_files_set = true;
    }
  }

  size_t parse_size(const std::string& size_str) {
    size_t multiplier = 1;
    std::string num_str;

    if (size_str.find("KB") != std::string::npos) {
      multiplier = 1024;
      num_str = size_str.substr(0, size_str.size() - 2);
    } else if (size_str.find("MB") != std::string::npos) {
      multiplier = 1024 * 1024;
      num_str = size_str.substr(0, size_str.size() - 2);
    } else if (size_str.find("GB") != std::string::npos) {
      multiplier = 1024 * 1024 * 1024;
      num_str = size_str.substr(0, size_str.size() - 2);
    } else {
      num_str = size_str;
    }

    return std::stoul(num_str) * multiplier;
  }

  std::string trim(const std::string& s) {
    auto start = s.begin();
    while (start != s.end() && std::isspace(*start)) start++;
    auto end = s.end();
    do {
      end--;
    } while (std::distance(start, end) > 0 && std::isspace(*end));
    return std::string(start, end + 1);
  }
  
  GlobalConfig global_config_;
  std::unordered_map<std::string, NodeConfig> node_configs_;
};
